/*****************************************************\
* Copyright (c) 2019 The Agile Authors. All rights reserved.
  Use of this source code is governed by a BSD-style license that can be
  found in the LICENSE file.

* Author:         dzlua - https://gitee.com/dzlua/
* Email:          505544956@qq.com
* FileName:       agile.hpp
* Create Time:    2019/01/18 11:34:47
* Description:    agile ui
\****************************************************/
#ifndef AGILE_H_
#define AGILE_H_

#include <string>

namespace agile {
  template <typename T>
  T min(T d1, T d2) { return (d1 < d2) ? d1 : d2; }
  template <typename T>
  T max(T d1, T d2) { return (d1 > d2) ? d1 : d2; }

  class Point {
    public:
      Point() : x_(0.f), y_(0.f) {}
      Point(float x, float y) : x_(x), y_(y) {}
      bool operator==(const Point &pt) const { return this->equal(pt); }
      bool operator!=(const Point &pt) const { return !this->equal(pt); }
      virtual ~Point() = default;
    public:
      float x() const { return x_; }
      float y() const { return y_; }
      void set_x(float x) { x_ = x; }
      void set_y(float y) { y_ = y; }
      void set(float x, float y) { x_ = x; y_ = y; }
      void move(float x, float y) { x_ += x; y_ += y; }
      bool equal(const Point &pt) const {
        if (pt.x() != x_) return false;
        if (pt.y() != y_) return false;
        return true;
      }
    protected:
      float x_, y_;
  };

  class Size {
    public:
      Size() : cx_(0.f), cy_(0.f) {}
      Size(float cx, float cy) : cx_(cx), cy_(cy) {}
      bool operator==(const Size &sz) const { return this->equal(sz); }
      bool operator!=(const Size &sz) const { return !this->equal(sz); }
      virtual ~Size() = default;
    public:
      float cx() const { return cx_; }
      float cy() const { return cy_; }
      void set_cx(float cx) { cx_ = cx; }
      void set_cy(float cy) { cy_ = cy; }
      void set(float cx, float cy) { cx_ = cx; cy_ = cy; }
      void scale(float x, float y) { cx_ += x; cy_ += y;}
      bool validate() const { return (cx_ > 0.f) && (cy_ > 0.f); }
      bool equal(const Size &sz) const {
         if (sz.cx() != cx_) return false;
         if (sz.cy() != cy_) return false;
         return true;
      }
    protected:
      float cx_, cy_;
  };

  class Rect {
    public:
      Rect() = default;
      Rect(Point &pt, Size &sz) : pt_(pt), sz_(sz) {}
      Rect(float x, float y, float cx, float cy) : pt_(x, y), sz_(cx, cy) {}
      bool operator==(const Point &pt) const { return this->equal(pt); }
      bool operator==(const Size &sz) const { return this->equal(sz); }
      bool operator==(const Rect &rc) const { return this->equal(rc); }
      bool operator!=(const Point &pt) const { return !this->equal(pt); }
      bool operator!=(const Size &sz) const { return !this->equal(sz); }
      bool operator!=(const Rect &rc) const { return !this->equal(rc); }
      void operator=(const Point &pt) { this->set_point(pt); }
      void operator=(const Size &sz) { this->set_size(sz); }
      void operator&=(const Rect &rc) { this->set_intersection(rc); }
      void operator|=(const Rect &rc) { this->set_union(rc); }
      virtual ~Rect() = default;
    public:
      void set_left(float left) { pt_.set_x(left); }
      void set_top(float top) { pt_.set_y(top); }
      void set_right(float right) { sz_.set_cx(right - pt_.x()); }
      void set_bottom(float bottom) { sz_.set_cy(bottom - pt_.y()); }
      void set_left_top(float left, float top) { pt_.set(left, top); }
      void set_left_top(const Point &pt) { pt_ = pt; }
      void set_right_bottom(float right, float bottom) { sz_.set(right - pt_.x(), bottom - pt_.y()); }
      void set_right_bottom(const Point &pt) { sz_.set(pt.x() - pt_.x(), pt.y() - pt_.y()); }
      void set_point(float x, float y) { pt_.set(x, y); }
      void set_point(const Point& pt) { pt_ = pt; }
      void set_size(float cx, float cy) { sz_.set(cx, cy); }
      void set_size(const Size& sz) { sz_ = sz; }
      void set(float x, float y, float cx, float cy) { pt_.set(x, y); sz_.set(cx, cy); }
      void set(const Point &pt, const Size &sz) { pt_ = pt; sz_ = sz; }
      void set_inset(const Rect &inset) { set_inset(inset.left(), inset.top(), inset.right(), inset.bottom()); }
      void set_inset(float left, float top, float right, float bottom) { pt_.move(left, top); sz_.scale(-left - right, -top - bottom); }
      float x() const { return pt_.x(); }
      float y() const { return pt_.y(); }
      float cx() const { return sz_.cx(); }
      float cy() const { return sz_.cy(); }
      Point point() const { return pt_; }
      Size size() const { return sz_; }
      float left() const { return pt_.x(); }
      float top() const { return pt_.y(); }
      float right() const { return pt_.x() + sz_.cx(); }
      float bottom() const { return pt_.y() + sz_.cy(); }
      Point left_top() const { return pt_; }
      Point right_bottom() const { return Point(right(), bottom()); }
      Point right_top() const { return Point(right(), top()); }
      Point left_bottom() const { return Point(left(), bottom()); }
      Point center() const { return Point(pt_.x() + sz_.cx() / 2, pt_.y() + sz_.cy() / 2); }
      void move(float x, float y) { pt_.move(x, y); }
      void scale(float x, float y) { sz_.scale(x, y); }
      void center_scale(float x, float y) { pt_.move(-x, -y); sz_.scale(x * 2, y * 2); }
      bool validate() const { return sz_.validate(); }
      bool equal(const Point &pt) const { return pt_.equal(pt); }
      bool equal(const Size &sz) const { return sz_.equal(sz); }
      bool equal(const Rect &rc) const {
        if (!pt_.equal(rc.point())) return false;
        if (!sz_.equal(rc.size())) return false;
        return true;
      }
      bool contains(float x, float y) const {
        if (x < this->left()) return false;
        if (x > this->right()) return false;
        if (y < this->top()) return false;
        if (y > this->bottom()) return false;
        return true;
      }
      bool contains(const Point &pt) const {
        if (pt.x() < this->left()) return false;
        if (pt.x() > this->right()) return false;
        if (pt.y() < this->top()) return false;
        if (pt.y() > this->bottom()) return false;
        return true;
      }
      bool inside(const Rect &rc) const {
        if (this->left() <= rc.left()) return false;
        if (this->top() <= rc.top()) return false;
        if (this->right() >= rc.right()) return false;
        if (this->bottom() >= rc.bottom()) return false;
        return true;
      }
      bool has_intersection(const Rect &rc) const {
        if (this->left() >= rc.right()) return false;
        if (this->right() < rc.left()) return false;
        if (this->bottom() < rc.top()) return false;
        return true;
      }
      void set_intersection(const Rect &rc) {
        float left = max(this->left(), rc.left());
        float top = max(this->top(), rc.top());
        float right = min(this->right(), rc.right());
        float bottom = min(this->bottom(), rc.bottom());
        this->set_left_top(left, top);
        this->set_right_bottom(right, bottom);
      }
      void set_union(const Rect &rc) {
        float left = min(this->left(), rc.left());
        float top = min(this->top(), rc.top());
        float right = max(this->right(), rc.right());
        float bottom = max(this->bottom(), rc.bottom());
        this->set_left_top(left, top);
        this->set_right_bottom(right, bottom);
      }
    protected:
      Point pt_;
      Size sz_;
  };

  class Color {
    public:
      Color() : r_(0.f), g_(0.f), b_(0.f), a_(-1.f) {}
      Color(float r, float g, float b, float a = 1.0f) { set(r, g, b, a); }
      bool operator==(const Color &color) const { return this->equal(color); }
      bool operator!=(const Color &color) const { return !this->equal(color); }
      virtual ~Color() = default;
    public:
      bool equal(const Color &color) const {
        if (color.r() != r_) return false;
        if (color.g() != g_) return false;
        if (color.b() != b_) return false;
        if (color.a() != a_) return false;
        return true;
      }
      bool used() const { return a_ >= 0.f; }
      float r() const { return r_; }
      float g() const { return g_; }
      float b() const { return b_; }
      float a() const { return a_; }
      void set(float r, float g, float b, float a = 1.0f) {
        r_ = adjust(r);
        g_ = adjust(g);
        b_ = adjust(b);
        a_ = adjust(a);
      }
      void set_r(float r) { r_ = adjust(r); if (!used()) a_ = 0.f; }
      void set_g(float g) { g_ = adjust(g); if (!used()) a_ = 0.f; }
      void set_b(float b) { b_ = adjust(b); if (!used()) a_ = 0.f; }
      void set_a(float a) { a_ = adjust(a); if (!used()) a_ = 0.f; }
    protected:
      float adjust(float v) {
        if (v < 0.f) return 0.f;
        if (v > 1.f) return 1.f;
        return v;
      }
    protected:
      float r_, g_, b_, a_;
  };

  class Image {
    public:
      Image() = default;
      virtual ~Image() = default;
    public:
      virtual Size size() const = 0;
    public:
      float width() const { return size().cx(); }
      float height() const { return size().cy(); }
  };

  class Render {
    public:
      Render() = default;
      virtual ~Render() = default;
    public:
      virtual bool draw_line(const Color &color, const Point &pt1, const Point &pt2, float width = 1.f) = 0;
      virtual bool draw_rect(const Color &color, const Rect &rc, float width = 1.f, float rx = 0.f, float ry = 0.f) const = 0;
      virtual bool draw_text(const Color &color, const Rect &rc, std::wstring &text) const = 0;
      virtual bool draw_image(const Image &image, const Rect &dst, const Rect &src) const = 0;
      virtual bool fill_rect(const Color &color, const Rect &rc, float rx = 0.f, float ry = 0.f) const = 0;
    public:
      virtual bool translate(const std::wstring &src, std::wstring &dst) const = 0;
      virtual Size text_size(const std::wstring &text) const = 0;
  };

  class Graphic {
    public:
      Graphic() : visible_(true) {}
      virtual ~Graphic() = default;
    public:
      void set_visible(bool visible) { visible_ = visible; }
      bool visible() const { return visible_; }
      float cx() const { size().cx(); }
      float cy() const { size().cy(); }
    public:
      virtual Size size() const { return Size(); }
      virtual void render(Render *render, const Rect &rc) = 0;
    protected:
      bool visible_;
  };

  class GraphicBoder : public Graphic {
    public:
      GraphicBoder() = default;
      virtual ~GraphicBoder() override = default;
    public:
      virtual void render(Render *render, const Rect &rc) override {
        if (!render) return;
        if (!visible()) return;
        if (!color_.used()) return;
        if (round_cx() < 0.f || round_cy() < 0.f) return;

        if ( (round_cx() > 0.f || round_cy() > 0.f)
          || (left_width() == top_width() && right_width() == bottom_width()
            && left_width() == right_width())) {
          if (left_width() > 0.f)
            render->draw_rect(color_, rc, left_width(), round_cx(), round_cy());
        } else {
          if (top_width() > 0.f)
            render->draw_line(color_, rc.left_top(), rc.right_top(), top_width());
          if (left_width() > 0.f)
            render->draw_line(color_, rc.left_top(), rc.left_bottom(), left_width());
          if (right_width() > 0.f)
            render->draw_line(color_, rc.right_bottom(), rc.right_top(), right_width());
          if (bottom_width() > 0.f)
            render->draw_line(color_, rc.right_bottom(), rc.left_bottom(), bottom_width());
        }
      }
    public:
      void set_width(float left, float top, float right, float bottom) {
        width_.set(left, top, right - left, bottom - top);
      }
      void set_width(float width) {
        width_.set_left_top(width, width);
        width_.set_right_bottom(width, width);
      }
      void set_color(const Color &color) { color_ = color; }
      void set_round(const Size &sz) { round_ = sz; }
      void set_round(float cx, float cy) { round_.set(cx, cy); }
      Rect width() const { return width_; }
      float left_width() const { return width_.left(); }
      float top_width() const { return width_.top(); }
      float right_width() const { return width_.right(); }
      float bottom_width() const { return width_.bottom(); }
      Color color() const { return color_; }
      Size round() const { return round_; }
      float round_cx() const { return round_.cx(); }
      float round_cy() const { return round_.cy(); }
    protected:
      Rect width_;
      Color color_;
      Size round_;
  };

  class GraphicFill : public Graphic {
    public:
      GraphicFill() = default;
      virtual ~GraphicFill() override = default;
    public:
      virtual void render(Render *render, const Rect &rc) override {
        if (!render) return;
        if (!visible()) return;
        if (!color_.used()) return;
        if (round_.cx() < 0.f || round_.cy() < 0.f) return;

        Rect drc = rc;
        drc.set_inset(inset_);
        if (!drc.validate()) return;

        render->fill_rect(color_, drc, round_.cx(), round_.cy());
      }
    public:
      void set_inset(float left, float top, float right, float bottom) {
        inset_.set(left, top, right - left, bottom - top);
      }
      void set_color(const Color &color) { color_ = color; }
      void set_round(const Size &sz) { round_ = sz; }
      void set_round(float cx, float cy) { round_.set(cx, cy); }
      Color color() const { return color_; }
      Size round() const { return round_; }
      Rect inset() const { return inset_; }
      float left_inset() const { return inset_.left(); }
      float top_inset() const { return inset_.top(); }
      float right_inset() const { return inset_.right(); }
      float bottom_inset() const { return inset_.bottom(); }
    protected:
      Rect inset_;
      Color color_;
      Size round_;
  };

  class GraphicText : public Graphic {
    public:
      GraphicText() : translate_(false) {}
      virtual ~GraphicText() override = default;
    public:
      virtual void render(Render *render, const Rect &rc) override {
        if (!render) return;
        if (!rc.validate()) return;
        if (!visible()) return;
        if (!color_.used()) return;

        if (translate_) {
          std::wstring text;
          if (render->translate(text_, text))
            render->draw_text(color_, rc, text);
        } else {
          render->draw_text(color_, rc, text_);
        }
      }
    public:
      void set_color(const Color &color) { color_ = color; }
      Color color() const { return color_; }
      void set_text(const std::wstring &text) { text_ = text; }
      std::wstring text() const { return text_; }
      void set_translate(bool translate) { translate_ = translate; }
      bool translate() const { return translate_; }
    protected:
      Color color_;
      std::wstring text_;
      bool translate_;
  };

  class GraphicImage : public Graphic {
    public:
      enum class Type : utf8_t {
        fill, center, scale9
      };
    public:
      GraphicImage() = default;
      virtual ~GraphicImage() override = default;
    public:
      virtual void render(Render *render, const Rect &rc) override {
         if (!render) return;
        if (!rc.validate()) return;
        if (!visible()) return;

        render.draw_image(image_, rc, rc_);
      }
    public:
      void set_image(const Image &image) { image_ = image; }
      void set_rect(const Rect &rc) { rc_ = rc; }
      Image image() const { return image_; }
      Rect rect() const { return rc_; }
    protected:
      Image image_;
      Rect rc_;
  };
}

#endif